home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The Fatted Calf
/
The Fatted Calf.iso
/
Applications
/
Publishing
/
ImagePortfolio
/
Source
/
PaletteMatrix.m
< prev
next >
Wrap
Text File
|
1994-04-01
|
20KB
|
788 lines
// -------------------------------------------------------------------------------------
// PaletteMatrix.m
// Martin D. Flynn, NeXT Computer, Inc.
// You may freely copy, distribute and reuse the code in this example.
// NeXT disclaims any warranty of any kind, expressed or implied, as to its
// fitness for any particular use.
// -------------------------------------------------------------------------------------
#import "PaletteMatrix.h"
#import <stdlib.h>
#import <stdio.h>
#import <string.h>
#import <mach/mach.h>
#import <objc/List.h>
#import <appkit/View.h>
#import <appkit/Text.h>
#import <appkit/NXImage.h>
#import <appkit/Pasteboard.h>
#import <appkit/FontManager.h>
#import <appkit/SavePanel.h>
#import <appkit/nextstd.h>
#import <dpsclient/wraps.h>
#import <math.h>
#import "ImagePortfolio.h" // main app
#import "Portfolio.h" // Matrix delegate
#import "PaletteCell.h" // Image view cells
// -------------------------------------------------------------------------------------
static id paletteCellClass = (id)nil;
static char *cutListPaths = (char*)nil;
static int sigPasteboard = -1;
static char *imagePathType = (char*)nil;
static id pasteFont = (id)nil;
// -------------------------------------------------------------------------------------
#define prefVERSION 1.02
#define CELLCLASS [PaletteCell class]
#define newCELL [protoCell copy]
#define VSPACER 2.0
#define freeLIST(L) { if (L) { [[L freeObjects] free]; L = (id)nil; } }
// -------------------------------------------------------------------------------------
@implementation PaletteMatrix
// -------------------------------------------------------------------------------------
/* new instance */
+ newFrame:(const NXRect *)fRect
{
return [[self allocFromZone:NXDefaultMallocZone()] initFrame:fRect];
}
/* initialize instance */
- initFrame:(const NXRect*)fRect
{
/* init globals */
if (!imagePathType) imagePathType = NXCopyStringBuffer("imagePathType");
/* init super */
[super initFrame:fRect];
if (!paletteCellClass) paletteCellClass = CELLCLASS;
/* init vars */
delegate = self;
pbCopyTiff = YES;
pbCopyPath = NO;
loadingCells = NO;
loadMutex = mutex_alloc();
/* image list container */
imageList = [[[List alloc] initCount:1] empty];
deleteList = (id)nil;
/* default protocell */
protoCell = [paletteCellClass new];
intercell.width = VSPACER;
intercell.height = VSPACER;
/* size */
[self setMode:NX_LISTMODE];
[self setEmptySelectionEnabled:YES];
[self setAutoscroll:YES];
[self setBackgroundGray:NX_LTGRAY];
[self setCellBackgroundGray:NX_LTGRAY];
[self setFlipped:YES];
return self;
}
/* free object */
- free
{
[imageList free]; // cells are freed by super
freeLIST(deleteList);
mutex_free(loadMutex);
return [super free];
}
/* free all cells */
- freeCells
{
[self clearSelectedCell];
[self renewRows:0 cols:0];
[cellList freeObjects];
[imageList empty];
[self sizeToCells];
freeLIST(deleteList);
[window setDocEdited:YES];
return self;
}
/* remove selected cells (return deleted list) */
- freeSelectedCells
{
int i;
id iCell, dList = [self selectedCellList];
/* return if no selected cells */
if (!dList) return (id)nil;
/* delete selected cells from matrix */
i = [dList count];
[self clearSelectedCell];
[self renewRows:0 cols:0];
mutex_lock(loadMutex);
while(i) {
iCell = [dList objectAt:--i];
[imageList removeObject:iCell];
[cellList removeObject:iCell];
[iCell setDelegate:(id)nil];
}
mutex_unlock(loadMutex);
[self sizeToCells];
[window setDocEdited:YES];
return dList;
}
// -------------------------------------------------------------------------------------
// set attributes
/* set delegate */
- setDelegate:anObject
{
delegate = anObject;
return self;
}
/* set to font */
- setFont:fontObj
{
int i = [self cellCount];
[super setFont:fontObj];
[window disableDisplay];
while(i) [[cellList objectAt:--i] setFont:fontObj];
[protoCell setFont:fontObj];
conFlags.calcSize = YES;
[window reenableDisplay];
return self;
}
- changeFont:sender
{
[self setFont:[[FontManager new] convertFont:[self font]]];
return self;
}
/* set preferences (THIS ASSUMES PROPER PREFERENCE BUFFER FORMAT) */
- (BOOL)setPreferences:(char*)prefBuff returnRows:(int*)rows cols:(int*)cols
{
NXSize size;
float ver, pointSize;
char fontName[256];
id o;
if (!prefBuff || !*prefBuff) return NO;
sscanf(prefBuff, "%f %f %f %d %d %s %f", &ver,
&size.width, &size.height, cols, rows, fontName, &pointSize);
[self setCellSize:&size];
if (o = [Font newFont:fontName size:pointSize]) [self setFont:o];
return YES;
}
// -------------------------------------------------------------------------------------
// pasteboard
- (char*)imagePaths:list
{
int i, c, len = 0;
char *fullPath, *p;
if (!(c = [list count])) return (char*)nil;
for (i=0;i<c;i++) { len += strlen([[list objectAt:i] imagePath]) + 1; }
fullPath = p = (char*)malloc(len + 1);
for (i=0;i<c;i++) { sprintf(p, "%s\t", [[list objectAt:i] imagePath]); p += strlen(p); }
*(p - 1) = 0; // remove trailing tab (p > fullPath is guaranteed)
return fullPath;
}
/* copy listed cells to pasteboard */
- copyToPasteboard:list paths:(char*)listPaths
{
char *dt[2], *data, *path;
int len, max, n = 0;
NXStream *stream;
id first, image, pb;
/* return if no items to copy */
if (![list count]) return self;
/* return if no selected cell */
pb = [Pasteboard newName:NXGeneralPboard];
if (!pb) { NXLogError("ImagePortfolio: no pasteboard"); return self; }
/* declare pasteboard types */
if (pbCopyPath) dt[n++] = (char*)NXAsciiPboardType;
if (pbCopyTiff) dt[n++] = (char*)NXTIFFPboardType;
dt[n++] = (char*)imagePathType;
[pb declareTypes:dt num:n owner:NXApp];
sigPasteboard = [pb changeCount];
/* get first image */
first = [list objectAt:0];
/* write image representation to pasteboard */
if (pbCopyTiff) {
stream = NXOpenMemory(NULL, 0, NX_WRITEONLY);
image = [first image];
[image setName:[first cellTitle]];
[image lockFocus];
[image writeTIFF:stream];
[image unlockFocus];
NXGetMemoryBuffer(stream, &data, &len, &max);
[pb writeType:NXTIFFPboardType data:data length:len];
NXClose(stream);
NXFreeObjectBuffer(data, len);
}
/* write image path name to pasteboard */
if (pbCopyPath && (path = (char*)[first imagePath])) {
[pb writeType:NXAsciiPboardType data:path length:strlen(path) + 1];
}
/* write custom type */
if (listPaths) [pb writeType:imagePathType data:listPaths length:strlen(listPaths) + 1];
return self;
}
/* copy TIFF from pasteboard */
- copyFromPasteboard
{
id pb, pSave, imageId;
char **types, *data, titleBuff[256], *tiffName = (char*)nil;
char *path = (char*)[NXApp lastPath];
int len;
NXStream *stream;
/* return if no pasteboard */
if (!(pb = [Pasteboard newName:NXGeneralPboard])) return (id)nil;
/* check for custom type */
for (types = (char**)[pb types]; *types && strcmp(*types,imagePathType); types++);
if (*types) {
if (![pb readType:imagePathType data:&data length:&len]) return (id)nil;
[delegate loadFileList:data :NO:NO];
vm_deallocate(task_self(), (vm_address_t)data, (vm_size_t)len);
return (id)nil;
}
/* search pasteboard types for a TIFF file */
for (types=(char**)[pb types]; *types && strcmp(*types,(char*)NXTIFFPboardType); types++);
if (!*types) return (id)nil;
/* read data from pasteboard */
if (![pb readType:NXTIFFPboardType data:&data length:&len]) return (id)nil;
/* single-pass loop */
for (;;) {
/* load TIFF image */
stream = NXOpenMemory(data, len, NX_READONLY);
if (imageId = [NXImage alloc]) {
NXSize size;
[imageId setDataRetained:YES];
[imageId initFromStream:stream];
[imageId getSize:&size];
if (!size.width || !size.height) { [imageId free]; imageId = (id)nil; }
}
if (!imageId) { NXCloseMemory(stream, NX_FREEBUFFER); break; }
/* display image */
sprintf(titleBuff, "%s - TIFF", ([imageId name]?[imageId name]:"NXImage"));
[delegate showImage:imageId title:titleBuff];
/* display save panel */
pSave = [SavePanel new];
[pSave setTitle:"Save TIFF file image"];
[pSave setPrompt:"File:"];
[pSave setRequiredFileType:"tiff"];
[pSave setDirectory:path];
if ([pSave runModalForDirectory:path file:""]) tiffName = (char*)[pSave filename];
/* save to file */
if (tiffName) NXSaveToFile(stream, tiffName);
NXCloseMemory(stream, NX_FREEBUFFER);
if (tiffName) [delegate loadFileList:tiffName :NO:NO];
/* end single-pass loop */
break;
}
/* free memory */
vm_deallocate(task_self(), (vm_address_t)data, (vm_size_t)len);
[delegate showImage:(id)nil title:""];
if (imageId) [imageId free];
return self;
}
// -------------------------------------------------------------------------------------
// first responder
#define delKEY 0x7F
/* never relinquish firstResponder */
- resignFirstResponder { return (id)nil; }
/* show preferences (menu connection should point here to allow disabling when necessary) */
- showPreferences:sender { return [NXApp showPreferences:self]; }
/* perform close window functions */
- performClose:sender { return [window performClose:sender]; }
- performMiniaturize:sender { return [window performMiniaturize:sender]; }
/* accept keydown event (check for delete key) */
- keyDown:(NXEvent*)e
{
if (e->data.key.charCode == delKEY) return [self delete:(id)nil];
return [super keyDown:e];
}
/* delete selected cells */
- delete:sender
{
freeLIST(deleteList);
deleteList = [self freeSelectedCells];
[self display];
return self;
}
/* copy previously deleted cells back to imageList */
- undelete:sender
{
int i, c;
id iCell;
if (!deleteList) return self;
for (c = [deleteList count], i = 0; i < c; i++) {
iCell = [deleteList objectAt:i];
if ([self findCellWithImageFilePath:(char*)[iCell imagePath]]) continue;
[iCell setDelegate:delegate];
[self addImageCell:iCell];
[window setDocEdited:YES];
}
[deleteList free];
deleteList = (id)nil;
[self resizeAndDisplay];
return self;
}
/* return true if undeletable */
- (BOOL)undeletable
{
return ([deleteList count])? YES : NO;
}
/* cut selected cells */
- cut:sender
{
[self delete:sender];
if (!deleteList) return self;
if (cutListPaths) { free(cutListPaths); cutListPaths = (char*)nil; }
cutListPaths = [self imagePaths:deleteList];
[self copyToPasteboard:deleteList paths:cutListPaths];
return self;
}
/* make a copy of the selected cells */
- copy:sender
{
id dList = [self selectedCellList];
if (!dList) return self;
if (cutListPaths) { free(cutListPaths); cutListPaths = (char*)nil; }
cutListPaths = [self imagePaths:dList];
[self copyToPasteboard:dList paths:cutListPaths];
[dList free];
return self;
}
/* copy cut cells back to imageList */
- paste:sender
{
if ([[Pasteboard new] changeCount] != sigPasteboard) [self copyFromPasteboard];
else [delegate loadFileList:cutListPaths :NO:NO];
[self resizeAndDisplay];
return self;
}
/* select all cells */
- selectAll:sender
{
int i;
if (mFlags.radioMode) return self;
i = [self imageListCount];
while(i) [[self imageAt:--i] setState:1];
[self display];
return self;
}
/* sort images by name */
- sortByCellTitle:sender
{
[self shellSort:@selector(compareCellTitle::)];
[self resizeAndDisplay];
return self;
}
/* copy font */
- copyFont:sender
{
pasteFont = [self font];
return self;
}
/* paste font */
- pasteFont:sender
{
if (pasteFont) [self setFont:pasteFont];
return self;
}
// -------------------------------------------------------------------------------------
// first responder: save
- (char*)getPreferenceString:(char*)buff
{
int rows, cols;
[delegate getDisplayedRows:&rows cols:&cols];
sprintf(buff, "%.2f %.0f %.0f %d %d %s %.0f", prefVERSION,
cellSize.width, cellSize.height, cols, rows, [font name], [font pointSize]);
return buff;
}
/* save file names */
- save:sender { return ([self imageListCount])? [delegate save:sender] : self; }
- saveAs:sender { return ([self imageListCount])? [delegate saveAs:sender] : self; }
/* save to file (called by delegate) */
- saveToFile:(char*)fileName
{
int i, c = [self imageListCount];
char buff[512];
FILE *fNum;
/* get file name to save */
if (!fileName || !c) return (id)nil;
/* open file */
fNum = fopen(fileName, "w");
if (!fNum) return (id)nil;
/* write preferences and path names */
fprintf(fNum, ": %s\n", [self getPreferenceString:buff]);
for (i = 0; i < c; i++) fprintf(fNum, "%s\n", [[self imageAt:i] imagePath]);
/* close file and indicate that window has been saved */
fclose(fNum);
[window setDocEdited:NO];
return self;
}
// -------------------------------------------------------------------------------------
// size
/* set intercell size */
- setIntercell:(const NXSize*)aSize
{
[super setIntercell:aSize];
[self sizeToCells];
return self;
}
/* set image size */
- setCellSize:(const NXSize*)size
{
int i = [self cellCount];
/* update cell size */
[super setCellSize:size];
/* notify cells of size change */
while(i) [[cellList objectAt:--i] setCellSize:size];
[protoCell setCellSize:size];
/* recalc sizes */
[self sizeToCells];
return self;
}
/* readjust matrix to fit cells */
- sizeToCells
{
int i, rows, cols, cnt;
id oldCell;
/* clear selected cells */
[self clearSelectedCell];
/* adjust rows/cols */
cnt = [self imageListCount];
cols = (int)((frame.size.width + intercell.width) / (cellSize.width + intercell.width));
if (cols < 1) cols = 1;
rows = (cnt + cols - 1) / cols;
if (rows < 1) rows = 1;
[self renewRows:rows cols:cols];
/* replace cells (and free old cells) */
for (i = 0; i < cnt; i++) {
oldCell = [cellList replaceObjectAt:i with:[self imageAt:i]];
if (oldCell && ![oldCell image]) [oldCell free];
}
/* now resize matrix */
[super sizeToCells];
return self;
}
/* resize matrix and redisplay */
- resizeAndDisplay
{
[self sizeToCells];
[self display];
return self;
}
/* return cellSize */
- (NXSize*)cellSize
{
return &cellSize;
}
/* return intercellSize */
- (NXSize*)intercell
{
return &intercell;
}
// -------------------------------------------------------------------------------------
// add cell methods
/* called by main thread when cell was added */
- _updateCells
{
[self resizeAndDisplay];
loadingCells = NO;
return self;
}
/* add new image */
- addImage:(const char*)filePath
{
id iCell;
/* see if its already in the list */
if ([self findCellWithImageFilePath:(char*)filePath]) return (id)nil;
/* add and show cell */
iCell = newCELL;
if (![iCell setImageFile:filePath]) { [iCell free]; return (id)nil; }
[iCell setDelegate:delegate];
[self addImageCell:iCell];
if (!loadingCells) {
loadingCells = YES;
[self mainThreadPerform:@selector(_updateCells) wait:NO];
}
return iCell;
}
/* return current number of image cells */
- (int)imageListCount
{
int cnt;
mutex_lock(loadMutex);
cnt = [imageList count];
mutex_unlock(loadMutex);
return cnt;
}
/* return indexed image cell */
- imageAt:(int)index
{
id iCell;
mutex_lock(loadMutex);
iCell = [imageList objectAt:index];
mutex_unlock(loadMutex);
return iCell;
}
/* add image cell */
- addImageCell:iCell
{
mutex_lock(loadMutex);
[imageList addObject:iCell];
mutex_unlock(loadMutex);
return iCell;
}
// -------------------------------------------------------------------------------------
// cell selection
/* return cell at spcified mouse location */
- getCellAtLocation:(NXPoint*)mousePt
{
float x, y;
float matWidth = (numCols * (cellSize.width + intercell.width )) - intercell.width ;
float matHeight = (numRows * (cellSize.height + intercell.height)) - intercell.height;
int i, r = -1, c = -1;
NXPoint pt = *mousePt;
/* convert location to matrix */
[self convertPoint:&pt fromView:(id)nil];
/* check location is within bounds */
if ((pt.x < bounds.origin.x) || (pt.x > bounds.origin.x + matWidth ) ||
(pt.y < bounds.origin.y) || (pt.y > bounds.origin.y + matHeight) ) return (id)nil;
/* find column */
for (x=bounds.origin.x, i=0; (i<numCols) && (pt.x>=x); i++, x+=intercell.width) {
x += cellSize.width;
if (pt.x < x) { c = i; break; }
}
if (c < 0) return (id)nil;
/* find row */
for (y=bounds.origin.y, i=0; (i<numRows) && (pt.y>=y); i++, y+=intercell.height) {
y += cellSize.height;
if (pt.y < y) { r = i; break; }
}
if (r < 0) return (id)nil;
/* return cell at location */
return [self cellAt:r :c];
}
/* intercept mouse down event */
- mouseDown:(NXEvent*)e
{
/* ignore while images are being loaded into palette */
if ([delegate isLoading]) return self;
/* check for Alternate-Shift pressed */
if ((e->flags) & NX_ALTERNATEMASK) {
NXPoint pt = e->location;
id cellId = [self getCellAtLocation:&pt];
NXRect rect = { { 0.0, 0.0 }, {48.0, 48.0 } };
if (!cellId || ![cellId imagePath]) return self;
[self convertPoint:&pt fromView:(id)nil];
rect.origin.x = pt.x - rect.size.width / 2.0;
rect.origin.y = pt.y - rect.size.height / 2.0;
[delegate _unregisterWindow];
[self dragFile:[cellId imagePath] fromRect:&rect slideBack:YES event:e];
[delegate _registerWindow];
return self;
}
/* return regular mouse-down event */
return [super mouseDown:e];
}
/* return true if there are any selectable cells */
- (BOOL)anySelectableCells
{
return [self imageListCount]? YES : NO;
}
/* find image with name */
- findCellWithImageFilePath:(char*)p
{
int i;
id o, f = (id)nil;
i = [self imageListCount];
while(i) if (!strcmp([(o=[self imageAt:--i]) imagePath], p)) { f = o; break; }
return f;
}
/* return list of selected cells */
- (int)selectedCellCount
{
int c = 0, i;
i = [self imageListCount];
while(i) if ([[self imageAt:--i] isSelected]) c++;
return c;
}
/* return first selected cell found */
- selectedCell
{
int i;
id c, r = (id)nil;
i = [self imageListCount];
while(i) if ([(c = [self imageAt:--i]) isSelected]) r = c;
return r;
}
/* return list of selected cells */
- selectedCellList
{
int i;
id c, list = listALLOC(2);
i = [self imageListCount];
while(i) if ([(c=[self imageAt:--i]) isSelected]) [list addObject:c];
if (![list count]) { [list free]; list = (id)nil; }
return list;
}
/* return string containing file names of all selected cells */
- (char*)selectedCellPaths
{
id list = [self selectedCellList];
char *fullPath = [self imagePaths:list];
[list free];
return fullPath;
}
// -------------------------------------------------------------------------------------
// sort compare methods
/* sort/compare by image name */
- (int)compareCellTitle:image1:image2
{
return strcasecmp([image1 cellTitle], [image2 cellTitle]);
}
// -------------------------------------------------------------------------------------
// sort images
/* indexed from 1 */
#define _COMPARE(I, J) ((int)[self perform:sortCompare with:(I) with:(J)])
#define _ELEM(i) [imageList objectAt:(i) - 1]
#define _setELEM(i, V) [imageList replaceObjectAt:(i) - 1 with:(V)]
/* shell sort: O(N ^ 1.5) */
- shellSort:(SEL)sortCompare
{
int i, j, h, N;
id elem, temp;
/* check for invalid compare method */
if (!sortCompare) return (id)nil;
/* lock imageList */
mutex_lock(loadMutex);
/* determine h: (1 + 3 + 3^2 + 3^3 + 3^4 + ...) [ h = pow(3,floor(log(2*N+1)/log(3))) ]*/
for (N = [imageList count], h = 1; h <= N; h = 3 * h + 1);
/* shell sort */
while (h > 1)
for (h /= 3, i = h + 1; i <= N; i++) {
elem = _ELEM(i);
for (j=i; (j>h) && (_COMPARE((temp=_ELEM(j-h)),elem)>0); j-=h) _setELEM(j, temp);
_setELEM(j, elem);
}
/* unlock imageList */
mutex_unlock(loadMutex);
return self;
}
@end